AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "shared.lua" )
include( 'shared.lua' )

function ENT:SpawnFunction( ply, tr )

	if ( !tr.Hit ) then return end
	
	local SpawnPos = tr.HitPos + tr.HitNormal * 16
	
	---Holy Hackfest Batman!
	-- Too tired to find a better way.
	local oldUndo = undo.Do_Undo
	function undo.Do_Undo( undo1 )
		
		--PrintTable(undo1)
		
		--Look for this entity's class in the undo
		-- and then remove it from the undo list
		for k,v in pairs(undo1.Entities) do
			if v:GetClass() == "ply_station" then 
				undo.ReplaceEntity(v, nil)
				return 
			end
			
		end
		
		oldUndo(undo1)
		
	end
	
	local ent = ents.Create( "ply_station" )
		ent:SetPos( SpawnPos )
	ent:Spawn()
	ent:Activate()
	return ent
	
end

function ENT:Initialize()

	self.Entity:SetModel( "models/johny-srka/arcade_machine_static.mdl" )
	
	self.Entity:PhysicsInit( SOLID_VPHYSICS )
	self.Entity:SetMoveType( MOVETYPE_VPHYSICS )
	self.Entity:SetSolid( SOLID_VPHYSICS )
	
	local phys = self.Entity:GetPhysicsObject()
	if (phys:IsValid()) then
		phys:Wake()
		phys:EnableMotion(false)
	end
	self:SetUseType(SIMPLE_USE)
	
	self.InUse = false
	self.lastThink = CurTime()
	self.lastUse = CurTime()
	constraint.Weld(self, GetWorldEntity(), 0, 0, 0, true)
end

function ENT:getShipSpawn()
	
	local shipSpawns = ents.FindByName("ship_spawn")
	local spawnCount = table.Count(shipSpawns)
	local rndIndex = math.random(1, spawnCount)
	
	if spawnCount > 0 then
		return shipSpawns[rndIndex]:GetPos()
	else
		return nil
	end
	
end

function ENT:CreateCam( ply )

	ply.ship.outCam = ents.Create("ship_cam")
	ply.ship.outCam:SetModel("models/dav0r/camera.mdl")
	ply.ship.outCam:SetColor(255,255,255,0)
	ply.ship.outCam:SetPos(ply.ship:GetPos() + (ply.ship:GetUp():GetNormal() * 3000)) -- fsply.ship:GetPos() - Vector(0,0,300))
	ply.ship.outCam:SetAngles(ply.ship.outCam:AlignAngles(ply.ship.outCam:GetForward():Angle(), ply.ship:GetUp():Angle() * -1))
	ply.ship.outCam:SetAngles(ply.ship.outCam:AlignAngles(ply.ship.outCam:GetUp():Angle(), ply.ship:GetForward():Angle()))
	ply.ship.outCam:Spawn()
	ply.ship.outCam:Activate()
	ply.ship.outCam:SetMoveType(MOVETYPE_VPHYSICS)
	ply.ship.outCam:SetSolid(SOLID_NONE)
	ply.ship.outCam.machine = self

	ply.ship.viewCam = ents.Create("prop_physics")
	ply.ship.viewCam:SetModel("models/dav0r/camera.mdl")
	ply.ship.viewCam:SetColor(255,255,255,0)
	ply.ship.viewCam:SetPos(ply.ship:GetAttachment(ply.ship:LookupAttachment("front_view")).Pos)
	ply.ship.viewCam:Spawn()
	ply.ship.viewCam:Activate()
	ply.ship.viewCam.machine = self
	--ply.ship.viewCam:SetParent(ply.ship)
	constraint.Weld(ply.ship, ply.ship.viewCam, 0, 0, 0, true)
	
end

function ENT:SpawnShip(ply)

	ply.ship = ents.Create("as_ship")
	local shipSpawn = self:getShipSpawn()
	if !(shipSpawn == nil) then
		ply.ship:SetPos(shipSpawn)
	else
		ply.ship:Remove()
		self.lastUse = CurTime()
		return
	end

	ply.ship:SetCollisionGroup(COLLISION_GROUP_WORLD)
	timer.Simple(3, function() if ValidEntity(ply.ship) then ply.ship:SetCollisionGroup(COLLISION_GROUP_NONE) end end)
	ply.ship.owner = ply
	ply.ship:Spawn()
	ply.ship.machine = self
	self:CreateCam(ply)
	
	if ply.inThird then
		ply:SetViewEntity(ply.ship.outCam)
	else
		ply:SetViewEntity(ply.ship.viewCam)
	end
	
	umsg.Start("deathCam", ply)
		umsg.Bool(false)
	umsg.End()
	
	ply.ship.lastShot = CurTime()
	
end

function ENT:StartGame(ply)

	ply.lives = 3 
	ply:SetDeaths(ply.lives)
	ply.extraLives = 1
	
	ply:SetMoveType(MOVETYPE_NONE)
	ply:StripWeapons()
	ply.machine = self
	ply.lastUse = CurTime()
	ply.inThird = false
	self:SpawnShip(ply)
	self.User = ply
	ply.score = 0
	self.InUse = true
	self.tareAngle = ply:EyeAngles()
	ply.IsUsing = true
	--ply:SetViewEntity(ply.ship.outCam)
	
	umsg.Start("inShip", ply)
		umsg.Bool(true)
	umsg.End()
				
	self:UpdatePlayerInfo(ply)
	
	ply.nextThrustSound = CurTime()
	ply.nextViewChange = CurTime()
	
end

function ENT:EndGame(ply)
	
	ply:SetMoveType(MOVETYPE_WALK)
	ply:SetViewEntity(ply)
	
	if ValidEntity(ply.ship) then
		if ValidEntity(ply.ship.outCam) then ply.ship.outCam:Remove() end
		if ValidEntity(ply.ship.viewCam) then ply.ship.viewCam:Remove() end
		ply.ship:Remove()
	end
	if ply.lives > 0 then
		ply.score = ply.score + (ply.lives * 500)
	end
	LogScore(ply)
	ply.score = 0
	ply:SetFrags(0)
	ply:SetDeaths(ply.lives)
	self.User = nil
	ply.machine = nil
	self.InUse = false
	ply.IsUsing = false
	
	umsg.Start("inShip", ply)
		umsg.Bool(false)
	umsg.End()
	
end

function ENT:UpdatePlayerInfo(ply)

	umsg.Start("updateLives", ply)
		umsg.Long(ply.lives)
	umsg.End()
	ply:SetDeaths(ply.lives)
	
	umsg.Start("updateScore", ply)
		umsg.Long(ply.score)
	umsg.End()
	ply:SetFrags(ply.score)
	
	umsg.Start("camPos", ply)
		umsg.Vector(ply.ship.outCam:GetPos())
	umsg.End()
	
	umsg.Start("inThird", ply)
		umsg.Bool(ply.inThird)
	umsg.End()
	
	
end

function ENT:Use(activator, caller)

	if self.lastUse == nil then self.lastUse = CurTime() - 2 end
	if CurTime() < self.lastUse + 1 then return end
	
	if activator:IsPlayer() then
		local ply = activator
		ply:ChatPrint("Controls:")
		ply:ChatPrint("W A S D Moves, the Mouse turns the ship.")
		ply:ChatPrint("Mouse1 Fires, Crouch changes views")
		ply:ChatPrint("Hold Sprint for speed boost and press f2 to see High scores")
		ply:ChatPrint("Press and Hold Use and Reload to end game early")
		if ply.IsUsing == nil then ply.IsUsing = false end
		if !self.InUse and
		   !ply.IsUsing then
				self.lastUse = CurTime()
				self:StartGame(ply)
		end
	end
	self.lastUse = CurTime()
end

function ENT:DestroyShip(ply)

	ply.ship.viewCam:Remove()
	local deathCamPos = ply.ship.outCam:GetPos() 
	local deathAng = (ply.ship:GetPos() - ply.ship.outCam:GetPos()):Angle()
	local joe = ply.ship.outCam
	
	ply:SetViewEntity(ply.ship.outCam)
	ply.ship.outCam:SetPos(deathCamPos)
	ply.ship.outCam:SetAngles(deathAng)

	timer.Simple(8, function() if ValidEntity(joe) then joe:Remove() end end)
	
	umsg.Start("camPos", ply)
		umsg.Vector(deathCamPos)
	umsg.End()
			
	umsg.Start("angGoal", ply)
		umsg.Angle(deathAng)
	umsg.End()	
	
	umsg.Start("firmCam", ply)
	umsg.End()
	
	umsg.Start("deathCam", ply)
		umsg.Bool(true)
	umsg.End()
	
	ply.ship:Destruct()

end

function ENT:Think() 

	if self.nextMusic == nil then self.nextMusic = CurTime() -1 end
	
	if self.InUse and
		!(self.User == nil) then
		local sBoost = 0
		local aBoost = 0
		
		if !ValidEntity(self.User) then
			self.User = nil
			self.InUse = false
			return
		end
		
		if !self.User:IsPlayer() then return end
		
		ply = self.User
	
		if CurTime() > self.nextMusic then
			local sndPos = ply:GetPos()
			if ValidEntity(ply.ship) then sndPos = ply.ship:GetPos() end
			WorldSound("as_music.wav",sndPos,85,100)
			WorldSound("as_music.wav",ply:GetPos(),85,100)
			self.nextMusic = CurTime() + SoundDuration("as_music.wav")
		end
		
		if !ValidEntity(ply.ship) then return end
		
		if ply.ship.destroyed then
			print("Ship Destroyed!")
			
			ply.lives = ply.lives - 1
			print(tostring(ply) .. " has " .. tostring(ply.lives) .. " lives remaining.")
			if ply.lives < 1 then 
				print("Game Over!")
				print(tostring(ply) .. " had " .. tostring(ply.score) .. " points.")
				self:DestroyShip(ply)
				timer.Simple(5, function(self) self:EndGame(ply) end, self)
				self:UpdatePlayerInfo(ply)
				return
			else
				print("Respawning Ship")
				self:DestroyShip(ply)
				timer.Simple(5, function(self) self:SpawnShip(ply) end, self)
				ply.score = ply.score - 500
				self:UpdatePlayerInfo(ply)
				return
			end
			
			
		end
		local shipMoveSpeed = 150
		if ply:KeyDown(IN_USE) and
		   ply:KeyDown(IN_RELOAD) then
			if (ply.lastUse + 0.5) < CurTime() then
				self:DestroyShip(ply)
				self:EndGame(ply)
			end
			ply.lastUse = CurTime()
		end
			
		if ply:KeyDown(IN_SPEED) then
			sBoost = 100
			aBoost = 0
		else
			sBoost = 0
			aBoost = 0
		end
		
		if ply:KeyDown(IN_ATTACK) then
			
			if CurTime() > ply.ship.lastShot + 0.25 then 
				local shootPos = ply.ship:GetAttachment(ply.ship:LookupAttachment("center_mount")).Pos
				local shot = ents.Create("laser_shot")
				shot:SetPos(shootPos + (ply.ship:GetForward() * 50))
				shot:Spawn()
				shot.owner = ply
				shot.machine = self
				shot:Shoot(ply.ship:GetForward():Angle(), 10000)
				shot:GetPhysicsObject():SetVelocity(shot:GetVelocity() + (ply.ship:GetVelocity() * 2))
				WorldSound("as_ship_fire.wav", shot:GetPos(), 150, 100)
				WorldSound("as_ship_fire.wav", ply:GetPos(), 150, 100)
				ply.ship.lastShot = CurTime()
			end
			
		end
		
		if ply:KeyDown(IN_MOVELEFT) then
			ply.ship:GetPhysicsObject():SetVelocity(ply.ship:GetVelocity() + (ply.ship:GetRight() * -(shipMoveSpeed + sBoost)))
			if CurTime() > ply.nextThrustSound then
				WorldSound("as_thrust.wav", ply.ship:GetPos(), 150, 100)
				WorldSound("as_thrust.wav", ply:GetPos(), 150, 100)
				ply.nextThrustSound = CurTime() + SoundDuration("as_thrust.wav")
			end
		end
		
		if ply:KeyDown(IN_MOVERIGHT) then
			ply.ship:GetPhysicsObject():SetVelocity(ply.ship:GetVelocity() + (ply.ship:GetRight() * (shipMoveSpeed + sBoost)))
			if CurTime() > ply.nextThrustSound then
				WorldSound("as_thrust.wav", ply.ship:GetPos(), 150, 100)
				WorldSound("as_thrust.wav", ply:GetPos(), 150, 100)
				ply.nextThrustSound = CurTime() + SoundDuration("as_thrust.wav")
			end
		end
		
		if ply:KeyDown(IN_FORWARD) then
			ply.ship:GetPhysicsObject():SetVelocity(ply.ship:GetVelocity() + (ply.ship:GetForward() * (shipMoveSpeed + sBoost)))
			if CurTime() > ply.nextThrustSound then
				WorldSound("as_thrust.wav", ply.ship:GetPos(), 150, 100)
				WorldSound("as_thrust.wav", ply:GetPos(), 150, 100)
				ply.nextThrustSound = CurTime() + SoundDuration("as_thrust.wav")
			end
		end
		
		if ply:KeyDown(IN_BACK) then
			ply.ship:GetPhysicsObject():SetVelocity(ply.ship:GetVelocity() + (ply.ship:GetForward() * -(shipMoveSpeed + sBoost)))
			if CurTime() > ply.nextThrustSound then
				WorldSound("as_thrust.wav", ply.ship:GetPos(), 150, 100)
				WorldSound("as_thrust.wav", ply:GetPos(), 150, 100)
				ply.nextThrustSound = CurTime() + SoundDuration("as_thrust.wav")
			end
		end
		
		if ply:KeyDown(IN_JUMP) then
			ply.ship:GetPhysicsObject():SetVelocity(ply.ship:GetVelocity() * 0.85)
		end
		
		if ply:KeyDown(IN_DUCK) then
			if CurTime() > ply.nextViewChange then
				ply.inThird = !ply.inThird
				
				if ply.inThird then
					ply:ChatPrint("Switching to Outside View")
					ply:SetViewEntity(ply.ship.outCam)
				else
					ply:ChatPrint("Switching to Inside View")
					ply:SetViewEntity(ply.ship.viewCam)
				end
				
				umsg.Start("inThird", ply)
					umsg.Bool(ply.inThird)
				umsg.End()
				
				self:UpdatePlayerInfo(ply)
				
				umsg.Start("firmCam", ply)
				umsg.End()
				
				ply.nextViewChange = CurTime() + 0.5
			end
		end
		
		
		local dirAngles = ply:EyeAngles() - self.tareAngle
		dirAngles = Angle(math.NormalizeAngle(dirAngles.p), math.NormalizeAngle(dirAngles.y), math.NormalizeAngle(dirAngles.r))
		if ply.inThird then 
			ply.ship:GetPhysicsObject():AddAngleVelocity(Angle(0,dirAngles.p* -1 ,dirAngles.y ))
		else
			ply.ship:GetPhysicsObject():AddAngleVelocity(Angle(0,dirAngles.p,dirAngles.y ))
		end
		
		local newEyeAngle = Angle(math.ApproachAngle(ply:EyeAngles().p, self.tareAngle.p, 25),
								  math.ApproachAngle(ply:EyeAngles().y, self.tareAngle.y, 25),
								  math.ApproachAngle(ply:EyeAngles().r, self.tareAngle.r, 25))
		
		ply:SetEyeAngles(newEyeAngle) 
		
		ply.ship:GetPhysicsObject():AddAngleVelocity(ply.ship:GetPhysicsObject():GetAngleVelocity() * -0.425)
		ply.ship:GetPhysicsObject():SetVelocity(ply.ship:GetVelocity() + (ply.ship:GetVelocity() * -0.025))
		
		local camPos = Vector(0,0,0)
		local camDist = camDist or 4096
		if camDist < 4096 then camDist = camDist + 0.05 end
		if camDist > 4096 then camDist = camDist - 0.05 end
		if camDist < 0 then camDist = 0 end
		if camDist > 8192 then camDist = 8192 end
		if !ply.ship.outCam:IsInWorld() then
			camDist = Lerp(0.7, camDist, 512)
		end
		
		camPos = (ply.ship:GetPos() + (ply.ship:GetUp():GetNormal() * (camDist + (ply.ship:GetVelocity():Length() * 2)))) + (ply.ship:GetForward() * 250)
		
		local camVel = ((ply.ship.outCam:GetPos() - camPos )) * -1
		ply.ship.outCam:GetPhysicsObject():SetVelocity(camVel)
		local viewCamPos = ply.ship:GetAttachment(ply.ship:LookupAttachment("front_view")).Pos
		if (viewCamPos - ply.ship.viewCam:GetPos()):Length() > 1000 then
			ply.ship.viewCam:SetPos(viewCamPos)
		end
		local myAng = (ply.ship:GetPos() - ply.ship.outCam:GetPos()):Angle()

		umsg.Start("camPos", ply)
			if ply.inThird then
				umsg.Vector(camPos)
			else
				umsg.Vector(ply.ship.viewCam:GetPos())
			end
		umsg.End()
			
		umsg.Start("angGoal", ply)
			if ply.inThird then
				umsg.Angle(myAng)
			else
				umsg.Angle(ply.ship:GetForward():Angle())
			end
		umsg.End()
		
		umsg.Start("shipVel", ply)
			umsg.Vector(ply.ship:GetVelocity())
		umsg.End()
		
		-- Code to find asteroids/shots for HUDMap
		local entsToCheck = {}
		for k,v in pairs(ents.GetAll()) do
			if v:GetClass() == "laser_shot" or
			   v:GetClass() == "asteroid" or
			   v:GetClass() == "as_ship" then
				table.insert(entsToCheck, v)
			end
		end
		
		local foundEnts = {}
		local mapZSize = 512
		for _, ent in pairs(entsToCheck) do
			if ValidEntity(ent) then
				local point = ent:GetPos()
				local p1 = self:planeRelation( {ply.ship:GetPos() + (ply.ship:GetUp() * mapZSize), 
				                                ply.ship:GetPos() + (ply.ship:GetUp() * mapZSize) + (ply.ship:GetForward() * 1024), 
												ply.ship:GetPos() + (ply.ship:GetUp() * mapZSize) + (ply.ship:GetForward() * 1024) + (ply.ship:GetRight() * 1024)}, 
												ent )
				local p2 = self:planeRelation( {ply.ship:GetPos() + ((ply.ship:GetUp() * -1) * mapZSize), 
				                                ply.ship:GetPos() + ((ply.ship:GetUp() * -1) * mapZSize) + (ply.ship:GetForward() * 1024), 
												ply.ship:GetPos() + ((ply.ship:GetUp() * -1) * mapZSize) + (ply.ship:GetForward() * 1024) + (ply.ship:GetRight() * 1024)}, 
												ent )
				if p1 < 0 and p2 > 0 then 
					table.insert(foundEnts, ent)
				end
			end
		end
				
		if table.Count(foundEnts) > 0 then
			for k, v in pairs(foundEnts) do
				umsg.Start("mapData", ply)
					if k == 1 then
						umsg.Bool(true)
					else
						umsg.Bool(false)
					end
					if v:GetClass() == "asteroid" then
						umsg.Char(1)
						umsg.Vector(WorldBox:WorldToLocal(v:GetPos()))
						umsg.Char(v.size)
					elseif v:GetClass() == "laser_shot" then
						umsg.Char(2)
						umsg.Vector(WorldBox:WorldToLocal(v:GetPos()))
						umsg.Char(0)
					elseif v:GetClass() == "as_ship" then
						umsg.Char(3)
						umsg.Vector(WorldBox:WorldToLocal(v:GetPos()))
						umsg.Char(1)
					end
				umsg.End()
			end
		else
			umsg.Start("mapData", ply)
				umsg.Bool(true)
			umsg.End()
		end

	end
	self.lastThink = CurTime()
	self:NextThink(CurTime())
end

function ENT:planeRelation(planePoints, ent)

		local p = 0
		local vN1 = (planePoints[1] - planePoints[2])
		local vN2 = (planePoints[3] - planePoints[2])
		local vNormal = vN1:Cross(vN2)
		vNormal = vNormal:GetNormal()
		
		local d = -planePoints[1]:DotProduct(vNormal)
		
		if ValidEntity(ent) then
			p = vNormal:DotProduct(ent:GetPos()) + d
			return p
		else
			return nil
		end
end
